package cn.hiapi.core.basic.redis.service.impl;

import cn.hiapi.core.basic.redis.service.RedisService;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author AdinZ
 * @date 2020/11/7 14:16
 */
@Service
@Configuration
public class RedisServiceImpl implements RedisService {
    private final RedisTemplate redisTemplate;

    public RedisServiceImpl(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public <K, V> V get(K key) {
        ValueOperations<K, V> operations = this.redisTemplate.opsForValue();
        return operations.get(key);
    }

    @Override
    public <K> void rename(K oldName, K newName) {
        this.redisTemplate.rename(oldName, newName);
    }

    @Override
    public <K, V> List<V> get(K k, long start, long end) {
        ListOperations<K, V> operations = this.redisTemplate.opsForList();
        return operations.range(k, start, end);
    }

    @Override
    public <K> Set<K> keys(K key) {
        return this.redisTemplate.keys(key);
    }

    public <K, V> void set(K k, V v) {
        ValueOperations<K, V> operations = this.redisTemplate.opsForValue();
        operations.set(k, v);
    }

    public <K, V> void set(K k, V v, long expire) {
        ValueOperations<K, V> operations = this.redisTemplate.opsForValue();
        operations.set(k, v, expire, TimeUnit.MILLISECONDS);
    }

    public <K> void delete(K k) {
        this.redisTemplate.delete(k);
    }

    @Override
    public <K> void delete(Collection<K> keys) {
        this.redisTemplate.delete(keys);
    }

    public <H, HK, HV> void set(H h, HK hk, HV hv) {
        this.setMap(h, hk, hv);
    }

    @Override
    public <H, HK, HV> void setMap(H h, HK hk, HV hv) {
        HashOperations<H, HK, HV> operations = this.redisTemplate.opsForHash();
        operations.put(h, hk, hv);
    }

    @Override
    public <H, HK, HV> void set(H h, Map<HK, HV> map) {
        HashOperations<H, HK, HV> operations = this.redisTemplate.opsForHash();
        operations.putAll(h, map);
    }

    @Override
    public <K, V> void set(K k, Collection<V> v) {
        ListOperations<K, V> operations = this.redisTemplate.opsForList();
        operations.leftPushAll(k, v);
    }

    public <H, HK, HV> HV get(H h, HK hk) {
        HashOperations<H, HK, HV> operations = this.redisTemplate.opsForHash();
        return operations.get(h, hk);
    }

    public <H, HK> void delete(H h, HK... hk) {
        HashOperations<H, HK, Object> operations = this.redisTemplate.opsForHash();
        operations.delete(h, hk);
    }

    public <H, HK, HV> Map<HK, HV> getMap(H h) {
        HashOperations<H, HK, HV> operations = this.redisTemplate.opsForHash();
        return operations.entries(h);
    }

    public <H, HV> List<HV> getMapValue(H h) {
        HashOperations<H, Object, HV> operations = this.redisTemplate.opsForHash();
        return operations.values(h);
    }

    public <H, HK> Set<HK> getMapKeys(H h) {
        HashOperations<H, HK, Object> operations = this.redisTemplate.opsForHash();
        return operations.keys(h);
    }

    @Override
    public <K> long increment(K key, long index) {
        return this.redisTemplate.opsForValue().increment(key, index);
    }

    @Override
    public <K> double increment(K key, double index) {
        return this.redisTemplate.opsForValue().increment(key, index);
    }

    @Override
    public <H, HK> long increment(H h, HK hk, long index) {
        return this.redisTemplate.opsForHash().increment(h, hk, index);
    }

    @Override
    public <H, HK> double increment(H h, HK hk, double index) {
        return this.redisTemplate.opsForHash().increment(h, hk, index);
    }

    @Override
    public synchronized <K> boolean lock(K k) {
        return this.lock(k, 15 * 1000);
    }


    @Override
    public <K> boolean lock(K k, long timeout) {


        String date = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SS");
        Boolean absent = this.redisTemplate.opsForValue().setIfAbsent(k, date, timeout, TimeUnit.MILLISECONDS);
        return absent.booleanValue();
//
//
//        Object o = this.get(k);
//        if (o != null) return false;
//        this.set(k, k, timeout);
//        return true;
    }

    @Override
    public <K> boolean isLock(K k) {
        return this.get(k) != null;
    }

    @Override
    public <K> void expire(K k, long expire) {
        this.redisTemplate.expire(k, expire, TimeUnit.MILLISECONDS);
    }

    @Override
    public <K> void unlock(K k) {
        delete(k);
    }


    public RedisTemplate getRedisTemplate() {
        return redisTemplate;
    }

    private IList listImpl = null;

    @Override
    public IList list() {
        if (listImpl == null) {
            synchronized (ListImpl.class) {
                listImpl = new ListImpl(this.redisTemplate);
            }
        }
        return listImpl;
    }

    private IMap mapImpl = null;

    @Override
    public IMap map() {
        if (mapImpl == null) {
            synchronized (MapImpl.class) {
                mapImpl = new MapImpl(this.redisTemplate);
            }
        }
        return mapImpl;
    }

    private IGeo geoImpl;


    @Override
    public IGeo geo() {

        if (geoImpl == null) {
            synchronized (GeoImpl.class) {
                geoImpl = new GeoImpl(this.redisTemplate);
            }
        }
        return geoImpl;
    }

    private class ListImpl implements IList {

        private final RedisTemplate template;

        public ListImpl(RedisTemplate template) {
            this.template = template;
        }


        @Override
        public <K, V> ListOperations<K, V> operations() {
            return this.template.opsForList();
        }

        @Override
        public <K, V> List<V> get(K k, long start, long end) {
            final ListOperations ops = this.template.opsForList();

            return ops.range(k, start, end);
        }

        @Override
        public <K, V> List<V> get(K k, long end) {
            return this.get(k, 0, end);
        }

        @Override
        public <K, V> V getRemove(K k) {
            return this.getRemove(k, 1000);
        }

        @Override
        public <K, V> V getRemove(K k, long timeout) {
            final ListOperations<K, V> ops = this.template.opsForList();
            return ops.leftPop(k, timeout, TimeUnit.MILLISECONDS);
        }

        @Override
        public <K> long size(K k) {
            final ListOperations<K, Object> ops = this.template.opsForList();
            final Long size = ops.size(k);
            return size == null ? 0 : size.longValue();
        }

        @Override
        public <K> void trim(K k, long start, long end) {
            final ListOperations<K, Object> ops = this.template.opsForList();
            ops.trim(k, start, end);
        }

        @Override
        public <K, V> Long add(K k, Collection<V> v) {
            final ListOperations<K, V> ops = this.template.opsForList();
            return ops.rightPushAll(k, v);
        }

        @Override
        public <K, V> Long addTop(K k, Collection<V> v) {
            final ListOperations<K, V> ops = this.template.opsForList();
            return ops.leftPushAll(k, v);
        }
    }

    private class MapImpl implements IMap {
        private final RedisTemplate template;

        public MapImpl(RedisTemplate template) {
            this.template = template;
        }

        @Override
        public <K, HK, HV> HashOperations<K, HK, HV> operations() {
            return this.template.opsForHash();
        }

        @Override
        public <K, HK, HV> void putAll(K key, Map<? extends HK, ? extends HV> m) {
            operations().putAll(key, m);
        }


    }

    private class GeoImpl implements IGeo {
        private final RedisTemplate template;
        private final GeoOperations operations;


        private GeoImpl(RedisTemplate template) {
            this.operations = template.opsForGeo();


            this.template = template;
        }


    }


}
